package awk.loader.jar_loader;

import sun.misc.URLClassPath;
import sun.net.www.protocol.file.FileURLConnection;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 将外部jar读入内存中
 */
public class DynamicJar {

    public static void main(String[] args) throws ClassNotFoundException {
        String jarFile = "/Users/yinqiqi/apache-maven-3.6.3/repository/cn/hutool/hutool-all/5.8.15/hutool-all-5.8.15.jar";
        String className = "cn.hutool.json.JSONUtil";
        //Class<?> aClass = loadClass(className, jarFile);
        //Class<?> aClass2 = loadClass2(className, jarFile);


        URLClassLoader urlClassLoader = loadJar(jarFile);
        unloadJar(urlClassLoader);
        Class<?> aClass3 = urlClassLoader.loadClass(className);
        String name = aClass3.getName();
    }

    public static  Class<?> loadClass(String className,String filePath) {
        try {
            URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            method.setAccessible(true);
            method.invoke(classLoader, new File(filePath).toURI().toURL());
            return classLoader.loadClass(className);
        } catch (NoSuchMethodException | MalformedURLException | InvocationTargetException | IllegalAccessException |
                 ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static Class<?> loadClass2(String className,String jarFilePath) {
        ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new File(jarFilePath).toURI().toURL()}, parentClassLoader);
            Thread.currentThread().setContextClassLoader(urlClassLoader);
            return urlClassLoader.loadClass(className);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public static URLClassLoader loadJar(String jarFilePath) {
        // 遍历 jar 包内的所有文件，如果判断是 .class 文件，则尝试加载
        try  {
            JarFile jarFile = new JarFile(new File(jarFilePath));
            URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new File(jarFilePath).toURI().toURL()});
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String name = jarEntry.getName();
                System.out.println("= entryName: " + name);
                if (!jarEntry.isDirectory() && name.endsWith(".class")) {
                    try {
                        Class<?> c = urlClassLoader.loadClass(name.substring(0, name.length() - 6).replaceAll("/", "."));
                        System.out.println("* urlClassLoader has load: " + c);
                    } catch (Throwable e) {
                        System.err.println("! throwable one: " + e.getClass().getName() + ", " + e.getMessage());
                    }
                }
            }
            return urlClassLoader;
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 该方法未验证。使用场景不多，使用场景未明确
     * @param classLoader
     */
    @Deprecated
    private static void unloadJar(URLClassLoader classLoader) {
        try {
            Field ucpField = URLClassLoader.class.getDeclaredField("ucp");
            ucpField.setAccessible(true);
            URLClassPath ucp = (URLClassPath) ucpField.get(classLoader);
            for (URL url : classLoader.getURLs()) {
                //URLClassPath$JarLoader URLClassPath.getLoader()
                Method getLoaderMethod = ucp.getClass().getDeclaredMethod("getLoader", URL.class);
                getLoaderMethod.setAccessible(true);
                //调用 URLClassPath.getLoader() 方法，返回 URLClassPath$JarLoader
                Object jarLoader = getLoaderMethod.invoke(ucp, url);

                //调用 URLClassPath$JarLoader.close()方法
                Method closeMethod = jarLoader.getClass().getDeclaredMethod("close");
                closeMethod.setAccessible(true);
                Object invoke = closeMethod.invoke(jarLoader);

                URLConnection urlConnection = url.openConnection();
                if (urlConnection instanceof JarURLConnection) {
                    urlConnection.setUseCaches(true);
                    ((JarURLConnection) urlConnection).getManifest();
                    JarURLConnection jarURLConnection = (JarURLConnection)urlConnection;
                    jarURLConnection.getJarFile().close();
                }else if (urlConnection instanceof FileURLConnection){
                    urlConnection.setUseCaches(true);
                    ((FileURLConnection)urlConnection).close();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


}
